#ifndef __XFUNC_HPP__
#define __XFUNC_HPP__

#include "math/value_helper.h"
#include "util/exception.h"
#include "math/xvalue.h"
#include "math/vector.hpp"
#include "math/math_constants.h"

NS_MATH_BEGIN

/* All functions/classes only deal with :
 * int64, float64, complex64,
 * arr_int64, arr_float64, arr_complex64
 */

// Auto-Vectorization Function Executer
template<typename func>
class func_p2p
{
public:
	template<typename T1, typename T2>
	static void g(T1& ret, const T2& v)
	{
		func::g(ret, v);
	}
	template<typename T1, typename T2>
	static void g(array<T1>& ret, const array<T2>& v)
	{
		ret.resize(v.size_table());
		for(array<T2>::size_type i=0;i<v.size();++i)
		{
			func::g(ret[i], v[i]);
		}
	}

	template<typename T1, typename T2>
	static void g(typename TypeCompT<T1,T2>::target_type& ret, const T1& v1, const T2& v2)
	{
		func::g<T1,T2>(ret, v1, v2);
	}
	template<typename T1, typename T2>
	static void g(typename TypeCompT<array<T1>, T2>::target_type& ret, const array<T1>& v1, const T2& v2)
	{
		ret.resize(v1.size_table());
		for(typename target_type::size_type i=0;i<ret.size();++i)
		{
			func::g(ret[i],v1[i],v2);
		}
	}
	template<typename T1, typename T2>
	static void g(typename TypeCompT<T1, array<T2> >::target_type& ret, const T1& v1, const array<T2>& v2)
	{
		ret.resize(v2.size_table());
		for(typename target_type::size_type i=0;i<ret.size();++i)
		{
			func::g(ret[i],v1,v2[i]);
		}
	}
	template<typename T1, typename T2>
	static void g(typename TypeCompT<array<T1>, array<T2> >::target_type& ret, const array<T1>& v1, const array<T2>& v2)
	{
		if(v1.size_match(v2))
		{
			// p2p mode
			ret.resize(v1.size_table());
			for(int i=0;i<ret.size();++i)
			{
				func::g(ret[i],v1[i],v2[i]);
			}
		}else
		{
			// Auto Broadcasting Check
			if(!(MathConfig::s_bAutoBroadcasting))
			{
				throw ::xphysics::util::exception("INVALID_OPERATION_WITHOUT_BROADCASTING");
			}
			// Do Auto Broadcasting Operation
			throw ::xphysics::util::exception("AUTO_BROADCASTING_UNCOMPLETED");
		}
	}
};

// Basic Operator

class assign
{
public:
	template<typename T1, typename T2>
	void g(T1& ret, const T2& val)
	{
		ret = (T1)val;
	}

	template<typename T>
	void g<T,T>(T& ret, const T& val)
	{
		ret = val;
	}

	template<typename T1, typename T2>
	void g(complex<T1>& ret, const T2& val)
	{
		ret.real = val;
	}

	template<typename T1, typename T2>
	void g(array<T1>& ret, const T2& val)
	{
		func_p2p<assign>::g(ret, val);
	}

	template<typename T1, typename T2>
	void g(array<T1>& ret, const array<T2>& val)
	{

	}
};

class add
{
public:
	template<typename T1, typename T2>
	void g(typename TypePromotionT<T1,T2>::type& ret, const T1& v1, const T2& v2)
	{
		typedef typename TypePromotionT<T1,T2>::type type;
		ret = (type)v1 + v2;
	}

	template<typename T1, typename T2>
	void g(typename TypePromotionT<complex<T1>, T2>::type& ret, const complex<T1>& v1, const T2& v2)
	{
		ret.real = v1.real + v2;
		ret.imag = v1.imag;
	}

	template<typename T1, typename T2>
	void g(typename TypePromotionT<T1, complex<T2> >::type& ret, const T1& v1, const complex<T2>& v2)
	{
		ret.real = v1 + v2.real;
		ret.imag = v2.imag;
	}

	template<typename T1, typename T2>
	void g(typename TypeCompT<complex<T1>, complex<T2> >::type& ret, const T1& v1, const T2& v2)
	{
		ret.real = TypeCompT<T1,T2>::type(v1.real) + v2.real;
		ret.imag = TypeCompT<T1,T2>::type(v1.imag) + v2.imag;
	}

	template<typename T1, typename T2>
	void g(typename TypeCompT<T1,T2>::target_type& ret, const T1& v1, const array<T2>& v2)
	{
		func_p2p<add>::g(ret, v1, v2);
	}


public:
	template<typename T1, typename T2>
	typename TypePromotionT<T1,T2>::type f(const T1& v1, const T2& v2)
	{
		typename TypePromotionT<T1,T2>::type ret;
		func_p2p<add>::g(ret, v1, v2);
		return ret;
	}

	}
};

// Function Definitions
class exp
{
public:
	// in-situ mode
	template<typename T>
	static void g(T& ret, const T& v){ret = std::exp(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		typedef double type0;
		type0 A = std::exp(v.real);
		ret.real = std::cos(v.imag) * A;
		ret.imag = std::sin(v.imag) * A;
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){return func_p2p<exp>::g(ret, v);}

	// ret-val mode
	template<typename T>
	static T f(const T& v)
	{
		T ret;
		g(ret, v);
		return ret;
	}
};

class ln
{
public:
	template<typename T>
	static void g(T& ret, const T& v){ret = std::log(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		ret.real = v.len();
		ret.imag = v.angle();
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){return func_p2p<ln>::g(ret, v);}

	template<typename T>
	static T f(const T& v)
	{
		T ret;
		g(ret, v);
		return ret;
	}
};

class log10
{
public:
	template<typename T>
	static void g(T& ret, const T& v){ret = std::log10(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		ret.real = v.len()/mc::ln10;
		ret.imag = v.angle()/mc::ln10;
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){return func_p2p<log10>::g(ret, v);}

	template<typename T>
	static T f(const T& v)
	{
		T ret;
		g(ret, v);
		return ret;
	}
};


class sin
{
public:

	template<typename T>
	static void g(T& ret, const T& v){ret = std::sin(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		/* sin of complex x(=a+bi)
		 * sin(x) = [e^(ix)-e^(-ix)]/(2i) ==> 1/2i = (-1/2)i
		 * sin(x) = [e^(-b+ai)-e^(b-ai)]/(2i)
		 * sin(x) = [e^(-b)(cos(a)+isin(a)) - e^b(cos(a)-isin(a))]/(2i)
		 * ******
		 & need exp(complex) first
		 */
		ret = v*mc::i;
		ret = (exp::f(ret) - exp::f(-ret))*(-0.5)*mc::i;
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){func_p2p<sin>::g(ret, v);}

	template<typename T>
	static T f(const T& v)
	{
		typedef T type;
		type ret;
		g(ret,v);
		return ret;
	}
};

class cos
{
public:

	template<typename T>
	static void g(T& ret, const T& v){ret = std::cos(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		/* cos of complex x(=a+bi)
		 * cos(x) = [e^(ix)+e^(-ix)]/2
		 & need exp(complex) first
		 */
		ret = v*mc::i;
		ret = (exp::f(ret) + exp::f(-ret))*0.5;
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){func_p2p<cos>::g(ret, v);}

	template<typename T>
	static T f(const T& v)
	{
		typedef T type;
		type ret;
		g(ret,v);
		return ret;
	}
};

class tan
{
public:
	template<typename T>
	static void g(T& ret, const T& v){ret = std::tan(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		/* tan of complex x(=a+bi)
		 * tan(x) = (exp(ix) - exp(-ix))/(exp(ix) + exp(-ix))
		 */
		complex<T> A = exp::f(v*mc::i);
		complex<T> B = exp::f(v*mc::j);
		ret = (A-B)/(A+B);
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){func_p2p<tan>::g(ret, v);}
	template<typename T>
	static T f(const T& v)
	{
		typedef T type;
		type ret;
		g(ret,v);
		return ret;
	}
};

class cot
{
public:
	template<typename T>
	static void g(T& ret, const T& v){ret = 1.0/std::tan(v);}
	template<typename T>
	static void g(complex<T>& ret, const complex<T>& v)
	{
		/* cot of complex x(=a+bi)
		 * cot(x) = (exp(ix) + exp(-ix))/(exp(ix) - exp(-ix))
		 */
		complex<T> A = exp::f(v*mc::i);
		complex<T> B = exp::f(v*mc::j);
		ret = (A+B)/(A-B);
	}
	template<typename T>
	static void g(array<T>& ret, const array<T>& v){func_p2p<cot>::g(ret, v);}
	template<typename T>
	static T f(const T& v)
	{
		typedef T type;
		type ret;
		g(ret,v);
		return ret;
	}
};

NS_MATH_END

#endif